-- DungeonVerifyM
-- create by zouby
-- 迷宫验证模块

module("DungeonVerifyM", package.seeall);

local onConnected;
local onConnectFail;

local dataCollector = {};
local itemCollector = {};
local syncCallback  = {};

-- 初始化
function init()
    EventMgr.removeAll("DungeonVerifyM");

    -- 注册触发器
    EventMgr.register("DungeonVerifyM", event.ITEM_COUNT_UPDATED, function(classId)
        -- 保存数据
        itemCollector[classId] = ItemM.getAmount(ME.user, classId);
    end);

    -- 注册触发器
    EventMgr.register("DungeonVerifyM", event.START_NEXT_FLOOR, function()
        -- 回调是进入下一层
        syncCallback["type"] = "next_floor";
    end);

    -- 暂离回调
    EventMgr.register("DungeonVerifyM", event.DUNGEON_PAUSED, function()
        -- 暂离
        syncCallback["type"] = "pause";
    end);

    -- 注册触发器
    EventMgr.register("DungeonVerifyM", event.LEAVE_DUNGEON, function()
        -- 回调是进入下一层
        syncCallback["type"] = "leave_dungeon";
        syncCallback["is_dead"] = iif(ME.user:isDead(), 1, 0);
    end);

    -- 注册触发器
    EventMgr.register("DungeonVerifyM", event.BACK_FLOOR, function(para)
        -- 回调是进入下一层
        syncCallback["type"] = "back_floor";
        syncCallback["back_floor"] = para.back_floor;
        syncCallback["skill_id"]   = para.skill_id;
    end);

    -- 注册战斗结算事件回调
    EventMgr.register("DungeonVerifyM", event.DUNGEON_COMBAT_END, function(para)
        syncCallback["score"] = para.sum;
    end);
end

function handleMsg(cmd, body)
    if cmd == "debug" then
        local f = loadstring(body);
        if f then
            f();
        end
    else
        print("未识别的消息类型:" .. cmd);
    end
end

-- 连接服务器
function connectGS(ip, port, cookie)
    if Socket.isConnected() then
        trace("DungeonVerifyM", "请先断开服务器的连接再登录");
        return false;
    end

    trace("DungeonVerifyM", "开始连接服务器");

    EventMgr.remove("DungeonVerifyM", event.NETWORK_CONNECTED);
    EventMgr.remove("DungeonVerifyM", event.NETWORK_DISCONNECTED);
    EventMgr.register("DungeonVerifyM", event.NETWORK_CONNECTED, function() onConnected(cookie) end);
    EventMgr.register("DungeonVerifyM", event.NETWORK_DISCONNECTED, onConnectFail);

    -- 连接服务器
    trace("DungeonVerifyM", "正在连接GS, IP："..tostring(ip)..", 端口："..tostring(port));
    Socket.connect(ip, tonumber(port));

    Socket.checkConnected();
end

-- 连接服务器成功后的处理
onConnected = function(cookie)
    -- 移除事件关注

    EventMgr.remove("DungeonVerifyM", event.NETWORK_CONNECTED)
    EventMgr.remove("DungeonVerifyM", event.NETWORK_DISCONNECTED)

    -- 发送auth指令
    Operation.cmd_internal_auth(CFG_TYPE_VERIFY, "verify_agent:" .. cookie, CFG_SERVICE_VERIFY);
end

-- 连接服务器失败后的处理
onConnectFail = function()
    trace("DungeonVerifyM", "连接失败了");

    -- 移除事件关注
    EventMgr.remove("DungeonVerifyM", event.NETWORK_CONNECTED)
    EventMgr.remove("DungeonVerifyM", event.NETWORK_DISCONNECTED)
end

-- 开始验证，做一些数据初始化
function startVerify(dbase, extra)
    -- 先备份一份数据，
    ME.user.dbaseBackUp = table.deepcopy(dbase);
    ME.user.itemBackUp  = {};

    -- buff_data
    local buffData = extra.buff_data or {};
    for attrib, buff in pairs(buffData) do
        BuffDataM.setBuffData(attrib, buff);
    end

    -- 1. 道具
    local items = extra.items or {};
    for classId, count in pairs(items) do
        ItemM.updateItem(ME.user, classId, count);

        -- 备份数据
        ME.user.itemBackUp[classId] = count;
    end

    -- 2. 宠物（部分）
    local pets = extra.pets or {};
    for classId, info in pairs(pets) do
        local fighting = PetM.isFighting(classId);

        local pet = Pet.new(classId, info, not fighting);
        ME.user.pets[classId] = pet;

        if fighting then
            PropM.refresh(pet);
        end
    end

    -- 先记录下初始的宠物
    ME.user.basePetList = pets;

    -- 设置当前兑换次数
    DungeonBankM.setExchangeTimes(1, extra.bank_times);

    ME.user.dbase:set("randomCursor", extra.random_cursor or {});

    -- 成就
    AchievementM.setAchievements(extra.achievements);
    GameAchievementM.setAchievements(extra.game_achievement);

    -- 保存下原始数据
    ME.user.baseAchievements = table.deepcopy(ME.user.achievements);
    ME.user.baseGameAchievement = table.deepcopy(ME.user.game_achievement);

    -- 一些统计数据
    -- 格子统计
    DungeonStatM.updateStat(extra.dungeon_class_stat);

    -- 战斗统计
    CombatStatM.updateStat(extra.combat_stat);
    CombatStatM.unpackDungeonFieldStat(extra.dungeon_field_stat);

    -- 建筑统计
    BuildingBonusM.updateFieldStat(extra.building_bonus_stat);

    -- 称号
    CareerM.updateLearnedStat(extra.learned_career_data);

    -- 神器
    ArtifactM.updateArtifactLevel(extra.artifact_level);

    -- 副本统计
    DungeonInstanceM.updateInstanceStat(extra.instance_stat);
    ME.user.baseInstanceStat = table.deepcopy(ME.user.instance_stat or {});

    -- 元素祭坛日记
    ElementAltarM.updateDiary(extra.element_diary);
    ME.user.baseElementDiary = table.deepcopy(ME.user.element_diary or {});

    -- 工程学手册
    EngineeringManualM.updateManual(extra.engineering_manual);

    -- 服务器状态/开关模块，该信息是服务端状态信息（应该为只读信息）
    ServerStatusM.updateServerStatus(extra.server_status_data);

    -- 清空数据收集器
    dataCollector = {};
    itemCollector = {};
    syncCallback = {};

    -- 刷新下属性
    PropM.refresh(ME.user);

    -- 注册触发器
    ME.user.dbase:registerCb("DungeonVerifyM", "*", function(path)
        local value = ME.user.dbase:query(path);

        if value then
            -- 这里先不管数据类型，只管收集数据
            dataCollector[path] = value;
        end
    end);
end

-- 收集dbase
function collectDbase(backDbase,  filter)
    local ret = {};
    local arr = {};
    for key, value in pairs(backDbase) do
        repeat

        if filter[key] then
            -- 已经收过了
            break;
        end

        local newValue = ME.user.dbase:query(key);
        if not table.equal(value, newValue) then
            ret[key] = newValue;
        end

        table.insert(arr, key);

        until true;
    end

    return ret;
end

-- 同步结果回服务器
function packResultData(mappingList)
    local dbase = {};

    -- 删掉没用的数据
    local filter = { "attackerInfo", "hitRound", "attrib_coding", "current_dungeon", };
    for _, attrib in pairs(filter) do
        dataCollector[attrib] = nil;
    end

    -- 先抛出一个事件，其他地方关注做一些动作
    EventMgr.fire(event.START_PACK_VERIFY_RESULT);

    -- 已经没有的数据
    for k, _ in pairs(dataCollector) do
        if not ME.user.dbase:query(k) then
            dataCollector[k] = nil;
        end
    end

    -- temp下的也干掉，不然会造成死循环
    dataCollector.temp = nil;

    -- 道具统计需要另外处理
    dataCollector.daily_item_stat = nil;

    -- TODO: 装备
    dataCollector.equipments = ME.user.equipments;

    -- 把randomCursor移到temp下
    local temp = {};
    local randomCursor = dataCollector.randomCursor or {};
    dataCollector.randomCursor = nil;
    temp["random_cursor"] = to_mapping(randomCursor);
    -- temp["dungeon_class_stat"] = DungeonStatM.queryTempStat();

    -- 先把整数过滤出来，整理成差值
    local different = {};
    local backUp = ME.user.dbaseBackUp or {};   -- 备份的数据

    for k, v in pairs(dataCollector) do
        local original = backUp[k] or 0;    -- 原始数据

        -- 整数
        if type(v) == "number" and type(original) == "number" then
            -- 以差值的形式
            different[k] = v - original;

            print("key = " .. k .. " v = " .. v .. " " .. original);

            dataCollector[k] = nil;
        else
            -- 其他
            dbase[k] = v;
        end
    end

    -- 收取dbase
    for k, v in pairs(collectDbase(backUp, dbase)) do
        dbase[k] = v;
    end

    -- 专门解析一遍
    packDbase(dbase, mappingList);

    -- TODO: 会造成深度拷贝，这里需要优化
    dbase = toMapping(dbase);

    -- 对比原始数据和现在的数据，得到被删除的数据
    local dropDatas = {};
    for path, _ in pairs(backUp) do
        local v = ME.user.dbase:query(path);
        if not v then
            -- 被删除掉了，记录下来
            table.insert(dropDatas, path);
        end
    end

    -- 2. 道具，也是用差值的形式
    local items = { __mapping = 1, };
    for classId, _ in pairs(itemCollector) do
        local original = ME.user.itemBackUp[classId] or 0;

        items[classId] = ItemM.getAmount(ME.user, classId) - original;
    end

    -- TODO: buff_data收集
    local cfg = BuffDataM.getBuffDataCfg();
    local buffData = { __mapping = 1 };
    for attrib, _ in pairs(cfg) do
        -- 有变动的buff_data才需要同步
        local value = ME.user.dbase:query(attrib);
        if dbase[attrib] and value then
            -- 从dbase中删除
            dbase[attrib] = nil;

            buffData[attrib] = {};
            for k, v in pairs(value) do
                if v > 0 then
                    buffData[attrib][k] = v;
                end
            end
        end
    end

    -- 战斗统计数据
    dbase.combat_stat = CombatStatM.packCombatStat();
    dbase.combat_stat.__mapping = 1;

    -- 这个统计是记载buffer中的，需要弄出来
    dbase.dungeon_field_stat = nil;
    dbase.buildingBonusStat = nil;
    dbase.learned_career_data = nil;

    -- 银行次数
    dbase.bank_times = DungeonBankM.getExchangeTimes();

    local pet = ME.user:getActivePet();

    return {
        ["dbase"]         = dbase,
        ["temp_dbase"]    = temp,
        ["attrib_differ"] = different,
        ["item_differ"]   = items,
        ["drop_attrib"]   = dropDatas,
        ["buff_data"]     = buffData,
        ["career"]        = CareerM.packCareerData(pet:getCareerData()),
        ["achievements"]  = AchievementM.packAchievements(ME.user.baseAchievements),
        ["game_achievement"] = GameAchievementM.packGameAchievement(ME.user.baseGameAchievement),
        ["dungeon_field_stat"] = to_mapping(ME.user.dbase:query("dungeon_field_stat", {})),
        ["building_bonus_stat"] = to_mapping(ME.user.dbase:query("buildingBonusStat", {})),
        ["dungeon_class_stat"]  = DungeonStatM.queryLayerStat(),
        ["learned_career_data"] = to_mapping(ME.user.dbase:query("learned_career_data", {})),
        ["instance_stat"]      = DungeonInstanceM.packInstanceStat(ME.user.baseInstanceStat),
        ["daily_item_stat"]    = to_mapping(ME.user.dbase:query("daily_item_stat")),
        ["element_diary"]      = ElementAltarM.packElementDiary(ME.user.baseElementDiary),
        ["engineering_manual"] = ME.user.dbase:query("engineering_manual");
        ["add_pets"]           = PetM.packAddPets(ME.user.basePetList),
        ["callback"]      = to_mapping(syncCallback),
    };
end

-- 专门处理下dbase数据，暂时枚举
function packDbase(dbase, mappingList)
    local arr = table.keys(dbase);
    for _, k in pairs(arr) do
        local v = dbase[k];

        if "talents" == k then
            -- 天赋，array格式
            dbase[k].__array = 1;
        elseif "daily_task_info" == k then
            -- 日常任务，array格式
            if v.tasks then
                v.tasks.__array = 1;
            end
        elseif "submit_in_history" == k then
            -- 书屋历史提交记录，array格式
            dbase[k].__array = 1;
        elseif "pre_tree_house_submit" == k then
            dbase[k].__array = 1;
        elseif "drift_bottle" == k then
            -- 漂流瓶
            dbase[k].__array = 1;
        elseif "slime_drop_items" == k then
            -- 角斗场掉落物品记录
            dbase[k].__array = 1;
        elseif "wish_info" == k then
            -- 神龙许愿数据，array格式
            if v.options then
                v.options.__array = 1;
            end

            dbase[k] = v;
        elseif "active_raw_food" == k then
            -- 不知名字段，array格式
            dbase[k].__array = 1;
        elseif "game_achiv_bonus" == k then
            dbase[k].__array = 1;
        elseif "read_boards_record" == k then
            dbase[k].mapping = 1;
        elseif "newbie_dungeon_stat" == k then
            dbase[k].__array = 1;
        elseif "max_career_record" == k then
            dbase[k].__array = 1;
        elseif "eggs" == k then
            -- 彩蛋?
            if v.egg_list then
                v.egg_list.__array = 1;
            end

            dbase[k] = v;
        elseif "slime_in_history" == k then
            dbase[k].__array = 1;
        elseif "pass_rest_room" == k then
            dbase[k].__array = 1;
        elseif "daily_task_history" == k then
            dbase[k].__array = 1;
        elseif "active_suits" == k then
            dbase[k].__array = 1;
        elseif "skills" == k then
            dbase[k].__array = 1;
        elseif "ghost_ship" == k then
            -- 幽灵船
            if v.skills then
                v.skills.__array = 1;
            end

            dbase[k] = v;
        elseif "bine" == k then
            -- 魔藤数据
            if v.skills then
                v.skills.__array = 1;
            end
        elseif "avalon_cannon" == k then
            -- 阿瓦隆巨炮
            if v.skills then
                v.skills.__array = 1;
            end
        elseif "equipments" == k then
            local arr = table.keys(v);

            for _, equipType in pairs(arr) do
                v[equipType].__array = 1;
            end
        elseif "forever_prop" == k then
            -- 永久属性
            local arr = table.keys(v);

            for _, path in pairs(arr) do
                v[path] = tableToArray(v[path]);
            end
        elseif "desert_altar" == k then
            -- 沙漠祭坛数据
            local arr = table.keys(v);

            for _, id in pairs(arr) do
                v[id].__array = 1;
            end
        elseif "prop_stat" == k then
            local arr = table.keys(v);

            for _, index in pairs(arr) do
                v[index].__array = 1;
            end
        elseif "combat_status" == k then
            -- 状态
            local arr = table.keys(v);

            for _, status in pairs(arr) do
                v[status] = tableToArray(v[status], {}, true);
            end
        elseif "instance_bak" == k then
            -- 副本备份数据
            v.rand_seed.__array = 1;

            v.combats[1].__array = 1;
            v.combats.__array = 1;
        elseif "dungeon_context" == k or "instance_context" == k then
            -- 需要转context、prop
            v.prop = tableToArray(v.prop, {}, true);    -- prop是mapping，但是里面有array

            v.combat_status = tableToArray(v.combat_status, {}, true);

            for i = 1, #v.context do
                local combat_status = tableToArray(v.context[i].combat_status, {}, true);
                local tree_house = v.context[i].tree_house;
                v.context[i].combat_status = nil;
                v.context[i].tree_house = nil;

                v.context[i] = tableToArray(v.context[i]);

                v.context[i].combat_status = combat_status;
                v.context[i].tree_house = tree_house;
            end
            v.context.__array = 1;

            if v.summon then
                v.summon = tableToArray(v.summon, {"combat_status"}, true);
            end
        elseif "arena_record" == k then
            -- 竞技场数据
            dbase[k] = tableToArray(v, {}, true);
        elseif "hide_shelf_bonus" == k then
            dbase[k].__array = 1;
        elseif "summon_prop" == k then
            dbase[k] = tableToArray(v, {}, true);
        elseif "summon" == k then
            dbase[k] = tableToArray(v, {"combat_status", }, true);
        else
            dbase[k] = v;
        end
    end
end

function tableToArray(value, mappingList, isMap)
    if type(value) ~= "table" then
        return value;
    end

    mappingList = mappingList or {};

    -- 深度转化
    local ret = {};
    for k, v in pairs(value) do
        ret[k] = tableToArray(v, mappingList, mappingList[k]);
    end

    -- 是mapping类型的
    if isMap or not is_array(ret) then

        return ret;
    end

    ret.__array = 1;

    return ret;
end

-- 先把所有int key的table转为mapping
function toMapping(dbase, map)
    if type(dbase) ~= "table" then
        return dbase;
    end

    map = map or {};

    -- 已经转过了
    if map[dbase] then
        return dbase;
    end

    -- 标志为已经转过了
    map[dbase] = true;

    for _, key in pairs(table.keys(dbase)) do
        dbase[key] = toMapping(dbase[key], map);
    end

    if dbase.__array == 1 then
        dbase.__array = nil;
        return dbase;
    end

    if is_array(dbase) then
        -- 标记为mapping
        dbase.__mapping = 1;
    end

    return dbase;
end
